package lsystem;

import java.util.ArrayList;
import java.util.Hashtable;

import symbol.LMethod;
import symbol.LMethodAdapter;
import symbol.LSymbol;
import symbol.LSymbolClass;

/**
 * 
 * @author marek
 *
 */
public class LGrammar {
	
	private String name = "LSystemsGrammar";
	private LSymbol axiom;
	
	private Hashtable<LSymbol, LProduction> productions = new Hashtable<LSymbol, LProduction>();
	
	/**
	 * If a method needs a parameter that is not defined in orginating symbol,
	 * it takes default one. 
	 */
	private Hashtable<String, Float> defaultParameters = new Hashtable<String, Float>();
	
	public ArrayList<LSymbol> generateSymbolSequence(int iterations){
		ArrayList<LSymbol> symbols = new ArrayList<LSymbol>();
		
		symbols.add(axiom);
		
		for(int i=0;i<iterations;i++)
			nextIteration(symbols);
		
		return symbols;
	}
	
	public ArrayList<LSymbol> nextIteration(ArrayList<LSymbol> symbols){
		
		int i=0;
		try
		{
			while(true){
				LSymbol symbol = symbols.get(i); 
				
				if(productions.containsKey(symbol)){
					ArrayList<LSymbolClass> rightSide = productions.get(symbol).getRightSide(); 
					
					LSymbol removedSymbol = symbols.remove(i);									
					
					//switch
					for(int j=0;j<rightSide.size();j++){
						LSymbol s = new LSymbol(rightSide.get(j));
							//Parametric calculations
							for(LMethod method: s.getSymbolClass().getMethods()){
								method.calculate(removedSymbol, s, defaultParameters); //NullPointer!
							}							
												
						symbols.add(i+j, s);
						
					}
					//symbols.addAll(i, rightSide);					
					
					i+=rightSide.size();
				}
				else
				{
					System.out.println("No production for "+symbol+" symbol.");
					i++;
				}
				
			}
		}
		catch (IndexOutOfBoundsException e)
		{			
			return symbols;
		}
		
		
	}
	
	
	public void addProduction(LProduction production){
		productions.put(production.getLeftSide(), production);
	}

	public LSymbol getAxiom()
	{
		return axiom;
	}

	public void setAxiom(LSymbol axiom)
	{
		this.axiom = axiom;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Hashtable<LSymbol, LProduction> getProductions()
	{
		return productions;
	}

	public void setProductions(Hashtable<LSymbol, LProduction> productions)
	{
		this.productions = productions;
	}
	
	public void setDefaultParameter(String name, Float value){
		defaultParameters.put(name, value);
	}
	
	public Float getDefaultParameter(String name){
		return defaultParameters.get(name);
	}
				
	public static LGrammar createTestGrammar(){
		LGrammar g = new LGrammar();
		g.setDefaultParameter("radius", 0.02f);
		LSymbol axiom = new LSymbol("B");		
		g.setAxiom(axiom);
		
		LProduction p = new LProduction();
		p.setLeftSide(new LSymbol("B"));		
		ArrayList<LSymbolClass> rightSide = new ArrayList<LSymbolClass>();	
		LSymbolClass radiusGrowth = new LSymbolClass("F", "F(r*1.8)");		
		radiusGrowth.addMethod(new LMethodAdapter(){
			public void calculate(LSymbol calculatedSymbol)
			{
				Float r = this.getParameter("radius");								
				calculatedSymbol.setParameter("radius", r*1.8f);
			}
		});		
		rightSide.add(radiusGrowth);
		
		LSymbolClass bClass = new LSymbolClass("B","B");
		LSymbolClass plusClass = new LSymbolClass("+","+");
		LSymbolClass minusClass = new LSymbolClass("-","-");
		LSymbolClass leftClass = new LSymbolClass("[","[");
		LSymbolClass rightClass = new LSymbolClass("]","]");
		//[-B][+B]
		rightSide.add(leftClass);
		rightSide.add(minusClass);
		rightSide.add(bClass);
		rightSide.add(rightClass);
		rightSide.add(leftClass);
		rightSide.add(plusClass);
		rightSide.add(bClass);
		rightSide.add(rightClass);
													
		p.setRightSide(rightSide);
		
		g.addProduction(p);
		
		LProduction p2 = new LProduction();
		p2.setLeftSide(new LSymbol("F"));
		ArrayList<LSymbolClass> rightSide2 = new ArrayList<LSymbolClass>();
		rightSide2.add(radiusGrowth);
		rightSide2.add(radiusGrowth);
		p2.setRightSide(rightSide2);
		
		g.addProduction(p2);
		
		return g;
		
	}
	
	public static void main(String[] args)
	{
		LGrammar g = new LGrammar();
		LSymbol axiom = new LSymbol("F");
		axiom.setParameter("radius", 0.01f);
		g.setAxiom(axiom);
		
		LProduction p = new LProduction();
		p.setLeftSide(new LSymbol("F"));		
		ArrayList<LSymbolClass> rightSide = new ArrayList<LSymbolClass>();	
		LSymbolClass symbolClass = new LSymbolClass("F", "F(r*1.8)");		
		//symbolClass.addMethod(new LMethod());		
		rightSide.add(symbolClass);
		
		LSymbolClass symbolClass2 = new LSymbolClass("F", "F(r*1.2)");		
		//symbolClass2.addMethod(new LMethod());
		rightSide.add(symbolClass2);		
		p.setRightSide(rightSide);
		
		g.addProduction(p);
		
		ArrayList<LSymbol> symbols = g.generateSymbolSequence(3);
		
		for(LSymbol s:symbols){
			System.out.print(s);
		}
		
	}
	
	
}
